3 * https://www.mediawiki.org/wiki/OOjs_UI
5 * Copyright 2011–2016 OOjs UI Team and other contributors.
6 * Released under the MIT license
7 * http://oojs.mit-license.org
9 * Date: 2016-10-03T18:59:01Z
16 * An ActionWidget is a {@link OO.ui.ButtonWidget button widget} that executes an action.
17 * Action widgets are used with OO.ui.ActionSet, which manages the behavior and availability
20 * Both actions and action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
21 * Please see the [OOjs UI documentation on MediaWiki] [1] for more information
24 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
27 * @extends OO.ui.ButtonWidget
28 * @mixins OO.ui.mixin.PendingElement
31 * @param {Object} [config] Configuration options
32 * @cfg {string} [action] Symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
33 * @cfg {string[]} [modes] Symbolic names of the modes (e.g., ‘edit’ or ‘read’) in which the action
34 * should be made available. See the action set's {@link OO.ui.ActionSet#setMode setMode} method
35 * for more information about setting modes.
36 * @cfg {boolean} [framed=false] Render the action button with a frame
38 OO
.ui
.ActionWidget
= function OoUiActionWidget( config
) {
39 // Configuration initialization
40 config
= $.extend( { framed
: false }, config
);
43 OO
.ui
.ActionWidget
.parent
.call( this, config
);
46 OO
.ui
.mixin
.PendingElement
.call( this, config
);
49 this.action
= config
.action
|| '';
50 this.modes
= config
.modes
|| [];
55 this.$element
.addClass( 'oo-ui-actionWidget' );
60 OO
.inheritClass( OO
.ui
.ActionWidget
, OO
.ui
.ButtonWidget
);
61 OO
.mixinClass( OO
.ui
.ActionWidget
, OO
.ui
.mixin
.PendingElement
);
66 * A resize event is emitted when the size of the widget changes.
74 * Check if the action is configured to be available in the specified `mode`.
76 * @param {string} mode Name of mode
77 * @return {boolean} The action is configured with the mode
79 OO
.ui
.ActionWidget
.prototype.hasMode = function ( mode
) {
80 return this.modes
.indexOf( mode
) !== -1;
84 * Get the symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
88 OO
.ui
.ActionWidget
.prototype.getAction = function () {
93 * Get the symbolic name of the mode or modes for which the action is configured to be available.
95 * The current mode is set with the action set's {@link OO.ui.ActionSet#setMode setMode} method.
96 * Only actions that are configured to be avaiable in the current mode will be visible. All other actions
101 OO
.ui
.ActionWidget
.prototype.getModes = function () {
102 return this.modes
.slice();
106 * Emit a resize event if the size has changed.
111 OO
.ui
.ActionWidget
.prototype.propagateResize = function () {
114 if ( this.isElementAttached() ) {
115 width
= this.$element
.width();
116 height
= this.$element
.height();
118 if ( width
!== this.width
|| height
!== this.height
) {
120 this.height
= height
;
121 this.emit( 'resize' );
131 OO
.ui
.ActionWidget
.prototype.setIcon = function () {
133 OO
.ui
.mixin
.IconElement
.prototype.setIcon
.apply( this, arguments
);
134 this.propagateResize();
142 OO
.ui
.ActionWidget
.prototype.setLabel = function () {
144 OO
.ui
.mixin
.LabelElement
.prototype.setLabel
.apply( this, arguments
);
145 this.propagateResize();
153 OO
.ui
.ActionWidget
.prototype.setFlags = function () {
155 OO
.ui
.mixin
.FlaggedElement
.prototype.setFlags
.apply( this, arguments
);
156 this.propagateResize();
164 OO
.ui
.ActionWidget
.prototype.clearFlags = function () {
166 OO
.ui
.mixin
.FlaggedElement
.prototype.clearFlags
.apply( this, arguments
);
167 this.propagateResize();
173 * Toggle the visibility of the action button.
175 * @param {boolean} [show] Show button, omit to toggle visibility
178 OO
.ui
.ActionWidget
.prototype.toggle = function () {
180 OO
.ui
.ActionWidget
.parent
.prototype.toggle
.apply( this, arguments
);
181 this.propagateResize();
186 /* eslint-disable no-unused-vars */
188 * ActionSets manage the behavior of the {@link OO.ui.ActionWidget action widgets} that comprise them.
189 * Actions can be made available for specific contexts (modes) and circumstances
190 * (abilities). Action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
192 * ActionSets contain two types of actions:
194 * - Special: Special actions are the first visible actions with special flags, such as 'safe' and 'primary', the default special flags. Additional special flags can be configured in subclasses with the static #specialFlags property.
195 * - Other: Other actions include all non-special visible actions.
197 * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
200 * // Example: An action set used in a process dialog
201 * function MyProcessDialog( config ) {
202 * MyProcessDialog.parent.call( this, config );
204 * OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
205 * MyProcessDialog.static.title = 'An action set in a process dialog';
206 * // An action set that uses modes ('edit' and 'help' mode, in this example).
207 * MyProcessDialog.static.actions = [
208 * { action: 'continue', modes: 'edit', label: 'Continue', flags: [ 'primary', 'constructive' ] },
209 * { action: 'help', modes: 'edit', label: 'Help' },
210 * { modes: 'edit', label: 'Cancel', flags: 'safe' },
211 * { action: 'back', modes: 'help', label: 'Back', flags: 'safe' }
214 * MyProcessDialog.prototype.initialize = function () {
215 * MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
216 * this.panel1 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
217 * this.panel1.$element.append( '<p>This dialog uses an action set (continue, help, cancel, back) configured with modes. This is edit mode. Click \'help\' to see help mode.</p>' );
218 * this.panel2 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
219 * this.panel2.$element.append( '<p>This is help mode. Only the \'back\' action widget is configured to be visible here. Click \'back\' to return to \'edit\' mode.</p>' );
220 * this.stackLayout = new OO.ui.StackLayout( {
221 * items: [ this.panel1, this.panel2 ]
223 * this.$body.append( this.stackLayout.$element );
225 * MyProcessDialog.prototype.getSetupProcess = function ( data ) {
226 * return MyProcessDialog.parent.prototype.getSetupProcess.call( this, data )
227 * .next( function () {
228 * this.actions.setMode( 'edit' );
231 * MyProcessDialog.prototype.getActionProcess = function ( action ) {
232 * if ( action === 'help' ) {
233 * this.actions.setMode( 'help' );
234 * this.stackLayout.setItem( this.panel2 );
235 * } else if ( action === 'back' ) {
236 * this.actions.setMode( 'edit' );
237 * this.stackLayout.setItem( this.panel1 );
238 * } else if ( action === 'continue' ) {
240 * return new OO.ui.Process( function () {
244 * return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
246 * MyProcessDialog.prototype.getBodyHeight = function () {
247 * return this.panel1.$element.outerHeight( true );
249 * var windowManager = new OO.ui.WindowManager();
250 * $( 'body' ).append( windowManager.$element );
251 * var dialog = new MyProcessDialog( {
254 * windowManager.addWindows( [ dialog ] );
255 * windowManager.openWindow( dialog );
257 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
261 * @mixins OO.EventEmitter
264 * @param {Object} [config] Configuration options
266 OO
.ui
.ActionSet
= function OoUiActionSet( config
) {
267 // Configuration initialization
268 config
= config
|| {};
270 // Mixin constructors
271 OO
.EventEmitter
.call( this );
276 actions
: 'getAction',
280 this.categorized
= {};
283 this.organized
= false;
284 this.changing
= false;
285 this.changed
= false;
287 /* eslint-enable no-unused-vars */
291 OO
.mixinClass( OO
.ui
.ActionSet
, OO
.EventEmitter
);
293 /* Static Properties */
296 * Symbolic name of the flags used to identify special actions. Special actions are displayed in the
297 * header of a {@link OO.ui.ProcessDialog process dialog}.
298 * See the [OOjs UI documentation on MediaWiki][2] for more information and examples.
300 * [2]:https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
307 OO
.ui
.ActionSet
.static.specialFlags
= [ 'safe', 'primary' ];
314 * A 'click' event is emitted when an action is clicked.
316 * @param {OO.ui.ActionWidget} action Action that was clicked
322 * A 'resize' event is emitted when an action widget is resized.
324 * @param {OO.ui.ActionWidget} action Action that was resized
330 * An 'add' event is emitted when actions are {@link #method-add added} to the action set.
332 * @param {OO.ui.ActionWidget[]} added Actions added
338 * A 'remove' event is emitted when actions are {@link #method-remove removed}
339 * or {@link #clear cleared}.
341 * @param {OO.ui.ActionWidget[]} added Actions removed
347 * A 'change' event is emitted when actions are {@link #method-add added}, {@link #clear cleared},
348 * or {@link #method-remove removed} from the action set or when the {@link #setMode mode} is changed.
355 * Handle action change events.
360 OO
.ui
.ActionSet
.prototype.onActionChange = function () {
361 this.organized
= false;
362 if ( this.changing
) {
365 this.emit( 'change' );
370 * Check if an action is one of the special actions.
372 * @param {OO.ui.ActionWidget} action Action to check
373 * @return {boolean} Action is special
375 OO
.ui
.ActionSet
.prototype.isSpecial = function ( action
) {
378 for ( flag
in this.special
) {
379 if ( action
=== this.special
[ flag
] ) {
388 * Get action widgets based on the specified filter: ‘actions’, ‘flags’, ‘modes’, ‘visible’,
391 * @param {Object} [filters] Filters to use, omit to get all actions
392 * @param {string|string[]} [filters.actions] Actions that action widgets must have
393 * @param {string|string[]} [filters.flags] Flags that action widgets must have (e.g., 'safe')
394 * @param {string|string[]} [filters.modes] Modes that action widgets must have
395 * @param {boolean} [filters.visible] Action widgets must be visible
396 * @param {boolean} [filters.disabled] Action widgets must be disabled
397 * @return {OO.ui.ActionWidget[]} Action widgets matching all criteria
399 OO
.ui
.ActionSet
.prototype.get = function ( filters
) {
400 var i
, len
, list
, category
, actions
, index
, match
, matches
;
405 // Collect category candidates
407 for ( category
in this.categorized
) {
408 list
= filters
[ category
];
410 if ( !Array
.isArray( list
) ) {
413 for ( i
= 0, len
= list
.length
; i
< len
; i
++ ) {
414 actions
= this.categorized
[ category
][ list
[ i
] ];
415 if ( Array
.isArray( actions
) ) {
416 matches
.push
.apply( matches
, actions
);
421 // Remove by boolean filters
422 for ( i
= 0, len
= matches
.length
; i
< len
; i
++ ) {
423 match
= matches
[ i
];
425 ( filters
.visible
!== undefined && match
.isVisible() !== filters
.visible
) ||
426 ( filters
.disabled
!== undefined && match
.isDisabled() !== filters
.disabled
)
428 matches
.splice( i
, 1 );
434 for ( i
= 0, len
= matches
.length
; i
< len
; i
++ ) {
435 match
= matches
[ i
];
436 index
= matches
.lastIndexOf( match
);
437 while ( index
!== i
) {
438 matches
.splice( index
, 1 );
440 index
= matches
.lastIndexOf( match
);
445 return this.list
.slice();
449 * Get 'special' actions.
451 * Special actions are the first visible action widgets with special flags, such as 'safe' and 'primary'.
452 * Special flags can be configured in subclasses by changing the static #specialFlags property.
454 * @return {OO.ui.ActionWidget[]|null} 'Special' action widgets.
456 OO
.ui
.ActionSet
.prototype.getSpecial = function () {
458 return $.extend( {}, this.special
);
462 * Get 'other' actions.
464 * Other actions include all non-special visible action widgets.
466 * @return {OO.ui.ActionWidget[]} 'Other' action widgets
468 OO
.ui
.ActionSet
.prototype.getOthers = function () {
470 return this.others
.slice();
474 * Set the mode (e.g., ‘edit’ or ‘view’). Only {@link OO.ui.ActionWidget#modes actions} configured
475 * to be available in the specified mode will be made visible. All other actions will be hidden.
477 * @param {string} mode The mode. Only actions configured to be available in the specified
478 * mode will be made visible.
483 OO
.ui
.ActionSet
.prototype.setMode = function ( mode
) {
486 this.changing
= true;
487 for ( i
= 0, len
= this.list
.length
; i
< len
; i
++ ) {
488 action
= this.list
[ i
];
489 action
.toggle( action
.hasMode( mode
) );
492 this.organized
= false;
493 this.changing
= false;
494 this.emit( 'change' );
500 * Set the abilities of the specified actions.
502 * Action widgets that are configured with the specified actions will be enabled
503 * or disabled based on the boolean values specified in the `actions`
506 * @param {Object.<string,boolean>} actions A list keyed by action name with boolean
507 * values that indicate whether or not the action should be enabled.
510 OO
.ui
.ActionSet
.prototype.setAbilities = function ( actions
) {
511 var i
, len
, action
, item
;
513 for ( i
= 0, len
= this.list
.length
; i
< len
; i
++ ) {
514 item
= this.list
[ i
];
515 action
= item
.getAction();
516 if ( actions
[ action
] !== undefined ) {
517 item
.setDisabled( !actions
[ action
] );
525 * Executes a function once per action.
527 * When making changes to multiple actions, use this method instead of iterating over the actions
528 * manually to defer emitting a #change event until after all actions have been changed.
530 * @param {Object|null} filter Filters to use to determine which actions to iterate over; see #get
531 * @param {Function} callback Callback to run for each action; callback is invoked with three
532 * arguments: the action, the action's index, the list of actions being iterated over
535 OO
.ui
.ActionSet
.prototype.forEach = function ( filter
, callback
) {
536 this.changed
= false;
537 this.changing
= true;
538 this.get( filter
).forEach( callback
);
539 this.changing
= false;
540 if ( this.changed
) {
541 this.emit( 'change' );
548 * Add action widgets to the action set.
550 * @param {OO.ui.ActionWidget[]} actions Action widgets to add
555 OO
.ui
.ActionSet
.prototype.add = function ( actions
) {
558 this.changing
= true;
559 for ( i
= 0, len
= actions
.length
; i
< len
; i
++ ) {
560 action
= actions
[ i
];
561 action
.connect( this, {
562 click
: [ 'emit', 'click', action
],
563 resize
: [ 'emit', 'resize', action
],
564 toggle
: [ 'onActionChange' ]
566 this.list
.push( action
);
568 this.organized
= false;
569 this.emit( 'add', actions
);
570 this.changing
= false;
571 this.emit( 'change' );
577 * Remove action widgets from the set.
579 * To remove all actions, you may wish to use the #clear method instead.
581 * @param {OO.ui.ActionWidget[]} actions Action widgets to remove
586 OO
.ui
.ActionSet
.prototype.remove = function ( actions
) {
587 var i
, len
, index
, action
;
589 this.changing
= true;
590 for ( i
= 0, len
= actions
.length
; i
< len
; i
++ ) {
591 action
= actions
[ i
];
592 index
= this.list
.indexOf( action
);
593 if ( index
!== -1 ) {
594 action
.disconnect( this );
595 this.list
.splice( index
, 1 );
598 this.organized
= false;
599 this.emit( 'remove', actions
);
600 this.changing
= false;
601 this.emit( 'change' );
607 * Remove all action widets from the set.
609 * To remove only specified actions, use the {@link #method-remove remove} method instead.
615 OO
.ui
.ActionSet
.prototype.clear = function () {
617 removed
= this.list
.slice();
619 this.changing
= true;
620 for ( i
= 0, len
= this.list
.length
; i
< len
; i
++ ) {
621 action
= this.list
[ i
];
622 action
.disconnect( this );
627 this.organized
= false;
628 this.emit( 'remove', removed
);
629 this.changing
= false;
630 this.emit( 'change' );
638 * This is called whenever organized information is requested. It will only reorganize the actions
639 * if something has changed since the last time it ran.
644 OO
.ui
.ActionSet
.prototype.organize = function () {
645 var i
, iLen
, j
, jLen
, flag
, action
, category
, list
, item
, special
,
646 specialFlags
= this.constructor.static.specialFlags
;
648 if ( !this.organized
) {
649 this.categorized
= {};
652 for ( i
= 0, iLen
= this.list
.length
; i
< iLen
; i
++ ) {
653 action
= this.list
[ i
];
654 if ( action
.isVisible() ) {
655 // Populate categories
656 for ( category
in this.categories
) {
657 if ( !this.categorized
[ category
] ) {
658 this.categorized
[ category
] = {};
660 list
= action
[ this.categories
[ category
] ]();
661 if ( !Array
.isArray( list
) ) {
664 for ( j
= 0, jLen
= list
.length
; j
< jLen
; j
++ ) {
666 if ( !this.categorized
[ category
][ item
] ) {
667 this.categorized
[ category
][ item
] = [];
669 this.categorized
[ category
][ item
].push( action
);
672 // Populate special/others
674 for ( j
= 0, jLen
= specialFlags
.length
; j
< jLen
; j
++ ) {
675 flag
= specialFlags
[ j
];
676 if ( !this.special
[ flag
] && action
.hasFlag( flag
) ) {
677 this.special
[ flag
] = action
;
683 this.others
.push( action
);
687 this.organized
= true;
694 * Errors contain a required message (either a string or jQuery selection) that is used to describe what went wrong
695 * in a {@link OO.ui.Process process}. The error's #recoverable and #warning configurations are used to customize the
696 * appearance and functionality of the error interface.
698 * The basic error interface contains a formatted error message as well as two buttons: 'Dismiss' and 'Try again' (i.e., the error
699 * is 'recoverable' by default). If the error is not recoverable, the 'Try again' button will not be rendered and the widget
700 * that initiated the failed process will be disabled.
702 * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button, which will try the
705 * For an example of error interfaces, please see the [OOjs UI documentation on MediaWiki][1].
707 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Processes_and_errors
712 * @param {string|jQuery} message Description of error
713 * @param {Object} [config] Configuration options
714 * @cfg {boolean} [recoverable=true] Error is recoverable.
715 * By default, errors are recoverable, and users can try the process again.
716 * @cfg {boolean} [warning=false] Error is a warning.
717 * If the error is a warning, the error interface will include a
718 * 'Dismiss' and a 'Continue' button. It is the responsibility of the developer to ensure that the warning
719 * is not triggered a second time if the user chooses to continue.
721 OO
.ui
.Error
= function OoUiError( message
, config
) {
722 // Allow passing positional parameters inside the config object
723 if ( OO
.isPlainObject( message
) && config
=== undefined ) {
725 message
= config
.message
;
728 // Configuration initialization
729 config
= config
|| {};
732 this.message
= message
instanceof jQuery
? message
: String( message
);
733 this.recoverable
= config
.recoverable
=== undefined || !!config
.recoverable
;
734 this.warning
= !!config
.warning
;
739 OO
.initClass( OO
.ui
.Error
);
744 * Check if the error is recoverable.
746 * If the error is recoverable, users are able to try the process again.
748 * @return {boolean} Error is recoverable
750 OO
.ui
.Error
.prototype.isRecoverable = function () {
751 return this.recoverable
;
755 * Check if the error is a warning.
757 * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button.
759 * @return {boolean} Error is warning
761 OO
.ui
.Error
.prototype.isWarning = function () {
766 * Get error message as DOM nodes.
768 * @return {jQuery} Error message in DOM nodes
770 OO
.ui
.Error
.prototype.getMessage = function () {
771 return this.message
instanceof jQuery
?
772 this.message
.clone() :
773 $( '<div>' ).text( this.message
).contents();
777 * Get the error message text.
779 * @return {string} Error message
781 OO
.ui
.Error
.prototype.getMessageText = function () {
782 return this.message
instanceof jQuery
? this.message
.text() : this.message
;
786 * A Process is a list of steps that are called in sequence. The step can be a number, a jQuery promise,
789 * - **number**: the process will wait for the specified number of milliseconds before proceeding.
790 * - **promise**: the process will continue to the next step when the promise is successfully resolved
791 * or stop if the promise is rejected.
792 * - **function**: the process will execute the function. The process will stop if the function returns
793 * either a boolean `false` or a promise that is rejected; if the function returns a number, the process
794 * will wait for that number of milliseconds before proceeding.
796 * If the process fails, an {@link OO.ui.Error error} is generated. Depending on how the error is
797 * configured, users can dismiss the error and try the process again, or not. If a process is stopped,
798 * its remaining steps will not be performed.
803 * @param {number|jQuery.Promise|Function} step Number of miliseconds to wait before proceeding, promise
804 * that must be resolved before proceeding, or a function to execute. See #createStep for more information. see #createStep for more information
805 * @param {Object} [context=null] Execution context of the function. The context is ignored if the step is
806 * a number or promise.
807 * @return {Object} Step object, with `callback` and `context` properties
809 OO
.ui
.Process = function ( step
, context
) {
814 if ( step
!== undefined ) {
815 this.next( step
, context
);
821 OO
.initClass( OO
.ui
.Process
);
828 * @return {jQuery.Promise} Promise that is resolved when all steps have successfully completed.
829 * If any of the steps return a promise that is rejected or a boolean false, this promise is rejected
830 * and any remaining steps are not performed.
832 OO
.ui
.Process
.prototype.execute = function () {
836 * Continue execution.
839 * @param {Array} step A function and the context it should be called in
840 * @return {Function} Function that continues the process
842 function proceed( step
) {
844 // Execute step in the correct context
846 result
= step
.callback
.call( step
.context
);
848 if ( result
=== false ) {
849 // Use rejected promise for boolean false results
850 return $.Deferred().reject( [] ).promise();
852 if ( typeof result
=== 'number' ) {
854 throw new Error( 'Cannot go back in time: flux capacitor is out of service' );
856 // Use a delayed promise for numbers, expecting them to be in milliseconds
857 deferred
= $.Deferred();
858 setTimeout( deferred
.resolve
, result
);
859 return deferred
.promise();
861 if ( result
instanceof OO
.ui
.Error
) {
862 // Use rejected promise for error
863 return $.Deferred().reject( [ result
] ).promise();
865 if ( Array
.isArray( result
) && result
.length
&& result
[ 0 ] instanceof OO
.ui
.Error
) {
866 // Use rejected promise for list of errors
867 return $.Deferred().reject( result
).promise();
869 // Duck-type the object to see if it can produce a promise
870 if ( result
&& $.isFunction( result
.promise
) ) {
871 // Use a promise generated from the result
872 return result
.promise();
874 // Use resolved promise for other results
875 return $.Deferred().resolve().promise();
879 if ( this.steps
.length
) {
880 // Generate a chain reaction of promises
881 promise
= proceed( this.steps
[ 0 ] )();
882 for ( i
= 1, len
= this.steps
.length
; i
< len
; i
++ ) {
883 promise
= promise
.then( proceed( this.steps
[ i
] ) );
886 promise
= $.Deferred().resolve().promise();
893 * Create a process step.
896 * @param {number|jQuery.Promise|Function} step
898 * - Number of milliseconds to wait before proceeding
899 * - Promise that must be resolved before proceeding
900 * - Function to execute
901 * - If the function returns a boolean false the process will stop
902 * - If the function returns a promise, the process will continue to the next
903 * step when the promise is resolved or stop if the promise is rejected
904 * - If the function returns a number, the process will wait for that number of
905 * milliseconds before proceeding
906 * @param {Object} [context=null] Execution context of the function. The context is
907 * ignored if the step is a number or promise.
908 * @return {Object} Step object, with `callback` and `context` properties
910 OO
.ui
.Process
.prototype.createStep = function ( step
, context
) {
911 if ( typeof step
=== 'number' || $.isFunction( step
.promise
) ) {
913 callback: function () {
919 if ( $.isFunction( step
) ) {
925 throw new Error( 'Cannot create process step: number, promise or function expected' );
929 * Add step to the beginning of the process.
931 * @inheritdoc #createStep
932 * @return {OO.ui.Process} this
935 OO
.ui
.Process
.prototype.first = function ( step
, context
) {
936 this.steps
.unshift( this.createStep( step
, context
) );
941 * Add step to the end of the process.
943 * @inheritdoc #createStep
944 * @return {OO.ui.Process} this
947 OO
.ui
.Process
.prototype.next = function ( step
, context
) {
948 this.steps
.push( this.createStep( step
, context
) );
953 * Window managers are used to open and close {@link OO.ui.Window windows} and control their presentation.
954 * Managed windows are mutually exclusive. If a new window is opened while a current window is opening
955 * or is opened, the current window will be closed and any ongoing {@link OO.ui.Process process} will be cancelled. Windows
956 * themselves are persistent and—rather than being torn down when closed—can be repopulated with the
957 * pertinent data and reused.
959 * Over the lifecycle of a window, the window manager makes available three promises: `opening`,
960 * `opened`, and `closing`, which represent the primary stages of the cycle:
962 * **Opening**: the opening stage begins when the window manager’s #openWindow or a window’s
963 * {@link OO.ui.Window#open open} method is used, and the window manager begins to open the window.
965 * - an `opening` event is emitted with an `opening` promise
966 * - the #getSetupDelay method is called and the returned value is used to time a pause in execution before
967 * the window’s {@link OO.ui.Window#getSetupProcess getSetupProcess} method is called on the
968 * window and its result executed
969 * - a `setup` progress notification is emitted from the `opening` promise
970 * - the #getReadyDelay method is called the returned value is used to time a pause in execution before
971 * the window’s {@link OO.ui.Window#getReadyProcess getReadyProcess} method is called on the
972 * window and its result executed
973 * - a `ready` progress notification is emitted from the `opening` promise
974 * - the `opening` promise is resolved with an `opened` promise
976 * **Opened**: the window is now open.
978 * **Closing**: the closing stage begins when the window manager's #closeWindow or the
979 * window's {@link OO.ui.Window#close close} methods is used, and the window manager begins
980 * to close the window.
982 * - the `opened` promise is resolved with `closing` promise and a `closing` event is emitted
983 * - the #getHoldDelay method is called and the returned value is used to time a pause in execution before
984 * the window's {@link OO.ui.Window#getHoldProcess getHoldProces} method is called on the
985 * window and its result executed
986 * - a `hold` progress notification is emitted from the `closing` promise
987 * - the #getTeardownDelay() method is called and the returned value is used to time a pause in execution before
988 * the window's {@link OO.ui.Window#getTeardownProcess getTeardownProcess} method is called on the
989 * window and its result executed
990 * - a `teardown` progress notification is emitted from the `closing` promise
991 * - the `closing` promise is resolved. The window is now closed
993 * See the [OOjs UI documentation on MediaWiki][1] for more information.
995 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
998 * @extends OO.ui.Element
999 * @mixins OO.EventEmitter
1002 * @param {Object} [config] Configuration options
1003 * @cfg {OO.Factory} [factory] Window factory to use for automatic instantiation
1004 * Note that window classes that are instantiated with a factory must have
1005 * a {@link OO.ui.Dialog#static-name static name} property that specifies a symbolic name.
1006 * @cfg {boolean} [modal=true] Prevent interaction outside the dialog
1008 OO
.ui
.WindowManager
= function OoUiWindowManager( config
) {
1009 // Configuration initialization
1010 config
= config
|| {};
1012 // Parent constructor
1013 OO
.ui
.WindowManager
.parent
.call( this, config
);
1015 // Mixin constructors
1016 OO
.EventEmitter
.call( this );
1019 this.factory
= config
.factory
;
1020 this.modal
= config
.modal
=== undefined || !!config
.modal
;
1022 this.opening
= null;
1024 this.closing
= null;
1025 this.preparingToOpen
= null;
1026 this.preparingToClose
= null;
1027 this.currentWindow
= null;
1028 this.globalEvents
= false;
1029 this.$ariaHidden
= null;
1030 this.onWindowResizeTimeout
= null;
1031 this.onWindowResizeHandler
= this.onWindowResize
.bind( this );
1032 this.afterWindowResizeHandler
= this.afterWindowResize
.bind( this );
1036 .addClass( 'oo-ui-windowManager' )
1037 .toggleClass( 'oo-ui-windowManager-modal', this.modal
);
1042 OO
.inheritClass( OO
.ui
.WindowManager
, OO
.ui
.Element
);
1043 OO
.mixinClass( OO
.ui
.WindowManager
, OO
.EventEmitter
);
1048 * An 'opening' event is emitted when the window begins to be opened.
1051 * @param {OO.ui.Window} win Window that's being opened
1052 * @param {jQuery.Promise} opening An `opening` promise resolved with a value when the window is opened successfully.
1053 * When the `opening` promise is resolved, the first argument of the value is an 'opened' promise, the second argument
1054 * is the opening data. The `opening` promise emits `setup` and `ready` notifications when those processes are complete.
1055 * @param {Object} data Window opening data
1059 * A 'closing' event is emitted when the window begins to be closed.
1062 * @param {OO.ui.Window} win Window that's being closed
1063 * @param {jQuery.Promise} closing A `closing` promise is resolved with a value when the window
1064 * is closed successfully. The promise emits `hold` and `teardown` notifications when those
1065 * processes are complete. When the `closing` promise is resolved, the first argument of its value
1066 * is the closing data.
1067 * @param {Object} data Window closing data
1071 * A 'resize' event is emitted when a window is resized.
1074 * @param {OO.ui.Window} win Window that was resized
1077 /* Static Properties */
1080 * Map of the symbolic name of each window size and its CSS properties.
1084 * @property {Object}
1086 OO
.ui
.WindowManager
.static.sizes
= {
1100 // These can be non-numeric because they are never used in calculations
1107 * Symbolic name of the default window size.
1109 * The default size is used if the window's requested size is not recognized.
1113 * @property {string}
1115 OO
.ui
.WindowManager
.static.defaultSize
= 'medium';
1120 * Handle window resize events.
1123 * @param {jQuery.Event} e Window resize event
1125 OO
.ui
.WindowManager
.prototype.onWindowResize = function () {
1126 clearTimeout( this.onWindowResizeTimeout
);
1127 this.onWindowResizeTimeout
= setTimeout( this.afterWindowResizeHandler
, 200 );
1131 * Handle window resize events.
1134 * @param {jQuery.Event} e Window resize event
1136 OO
.ui
.WindowManager
.prototype.afterWindowResize = function () {
1137 if ( this.currentWindow
) {
1138 this.updateWindowSize( this.currentWindow
);
1143 * Check if window is opening.
1145 * @return {boolean} Window is opening
1147 OO
.ui
.WindowManager
.prototype.isOpening = function ( win
) {
1148 return win
=== this.currentWindow
&& !!this.opening
&& this.opening
.state() === 'pending';
1152 * Check if window is closing.
1154 * @return {boolean} Window is closing
1156 OO
.ui
.WindowManager
.prototype.isClosing = function ( win
) {
1157 return win
=== this.currentWindow
&& !!this.closing
&& this.closing
.state() === 'pending';
1161 * Check if window is opened.
1163 * @return {boolean} Window is opened
1165 OO
.ui
.WindowManager
.prototype.isOpened = function ( win
) {
1166 return win
=== this.currentWindow
&& !!this.opened
&& this.opened
.state() === 'pending';
1170 * Check if a window is being managed.
1172 * @param {OO.ui.Window} win Window to check
1173 * @return {boolean} Window is being managed
1175 OO
.ui
.WindowManager
.prototype.hasWindow = function ( win
) {
1178 for ( name
in this.windows
) {
1179 if ( this.windows
[ name
] === win
) {
1188 * Get the number of milliseconds to wait after opening begins before executing the ‘setup’ process.
1190 * @param {OO.ui.Window} win Window being opened
1191 * @param {Object} [data] Window opening data
1192 * @return {number} Milliseconds to wait
1194 OO
.ui
.WindowManager
.prototype.getSetupDelay = function () {
1199 * Get the number of milliseconds to wait after setup has finished before executing the ‘ready’ process.
1201 * @param {OO.ui.Window} win Window being opened
1202 * @param {Object} [data] Window opening data
1203 * @return {number} Milliseconds to wait
1205 OO
.ui
.WindowManager
.prototype.getReadyDelay = function () {
1210 * Get the number of milliseconds to wait after closing has begun before executing the 'hold' process.
1212 * @param {OO.ui.Window} win Window being closed
1213 * @param {Object} [data] Window closing data
1214 * @return {number} Milliseconds to wait
1216 OO
.ui
.WindowManager
.prototype.getHoldDelay = function () {
1221 * Get the number of milliseconds to wait after the ‘hold’ process has finished before
1222 * executing the ‘teardown’ process.
1224 * @param {OO.ui.Window} win Window being closed
1225 * @param {Object} [data] Window closing data
1226 * @return {number} Milliseconds to wait
1228 OO
.ui
.WindowManager
.prototype.getTeardownDelay = function () {
1229 return this.modal
? 250 : 0;
1233 * Get a window by its symbolic name.
1235 * If the window is not yet instantiated and its symbolic name is recognized by a factory, it will be
1236 * instantiated and added to the window manager automatically. Please see the [OOjs UI documentation on MediaWiki][3]
1237 * for more information about using factories.
1238 * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
1240 * @param {string} name Symbolic name of the window
1241 * @return {jQuery.Promise} Promise resolved with matching window, or rejected with an OO.ui.Error
1242 * @throws {Error} An error is thrown if the symbolic name is not recognized by the factory.
1243 * @throws {Error} An error is thrown if the named window is not recognized as a managed window.
1245 OO
.ui
.WindowManager
.prototype.getWindow = function ( name
) {
1246 var deferred
= $.Deferred(),
1247 win
= this.windows
[ name
];
1249 if ( !( win
instanceof OO
.ui
.Window
) ) {
1250 if ( this.factory
) {
1251 if ( !this.factory
.lookup( name
) ) {
1252 deferred
.reject( new OO
.ui
.Error(
1253 'Cannot auto-instantiate window: symbolic name is unrecognized by the factory'
1256 win
= this.factory
.create( name
);
1257 this.addWindows( [ win
] );
1258 deferred
.resolve( win
);
1261 deferred
.reject( new OO
.ui
.Error(
1262 'Cannot get unmanaged window: symbolic name unrecognized as a managed window'
1266 deferred
.resolve( win
);
1269 return deferred
.promise();
1273 * Get current window.
1275 * @return {OO.ui.Window|null} Currently opening/opened/closing window
1277 OO
.ui
.WindowManager
.prototype.getCurrentWindow = function () {
1278 return this.currentWindow
;
1284 * @param {OO.ui.Window|string} win Window object or symbolic name of window to open
1285 * @param {Object} [data] Window opening data
1286 * @return {jQuery.Promise} An `opening` promise resolved when the window is done opening.
1287 * See {@link #event-opening 'opening' event} for more information about `opening` promises.
1290 OO
.ui
.WindowManager
.prototype.openWindow = function ( win
, data
) {
1292 opening
= $.Deferred();
1294 // Argument handling
1295 if ( typeof win
=== 'string' ) {
1296 return this.getWindow( win
).then( function ( win
) {
1297 return manager
.openWindow( win
, data
);
1302 if ( !this.hasWindow( win
) ) {
1303 opening
.reject( new OO
.ui
.Error(
1304 'Cannot open window: window is not attached to manager'
1306 } else if ( this.preparingToOpen
|| this.opening
|| this.opened
) {
1307 opening
.reject( new OO
.ui
.Error(
1308 'Cannot open window: another window is opening or open'
1313 if ( opening
.state() !== 'rejected' ) {
1314 // If a window is currently closing, wait for it to complete
1315 this.preparingToOpen
= $.when( this.closing
);
1316 // Ensure handlers get called after preparingToOpen is set
1317 this.preparingToOpen
.done( function () {
1318 if ( manager
.modal
) {
1319 manager
.toggleGlobalEvents( true );
1320 manager
.toggleAriaIsolation( true );
1322 manager
.currentWindow
= win
;
1323 manager
.opening
= opening
;
1324 manager
.preparingToOpen
= null;
1325 manager
.emit( 'opening', win
, opening
, data
);
1326 setTimeout( function () {
1327 win
.setup( data
).then( function () {
1328 manager
.updateWindowSize( win
);
1329 manager
.opening
.notify( { state
: 'setup' } );
1330 setTimeout( function () {
1331 win
.ready( data
).then( function () {
1332 manager
.opening
.notify( { state
: 'ready' } );
1333 manager
.opening
= null;
1334 manager
.opened
= $.Deferred();
1335 opening
.resolve( manager
.opened
.promise(), data
);
1337 manager
.opening
= null;
1338 manager
.opened
= $.Deferred();
1340 manager
.closeWindow( win
);
1342 }, manager
.getReadyDelay() );
1344 manager
.opening
= null;
1345 manager
.opened
= $.Deferred();
1347 manager
.closeWindow( win
);
1349 }, manager
.getSetupDelay() );
1353 return opening
.promise();
1359 * @param {OO.ui.Window|string} win Window object or symbolic name of window to close
1360 * @param {Object} [data] Window closing data
1361 * @return {jQuery.Promise} A `closing` promise resolved when the window is done closing.
1362 * See {@link #event-closing 'closing' event} for more information about closing promises.
1363 * @throws {Error} An error is thrown if the window is not managed by the window manager.
1366 OO
.ui
.WindowManager
.prototype.closeWindow = function ( win
, data
) {
1368 closing
= $.Deferred(),
1371 // Argument handling
1372 if ( typeof win
=== 'string' ) {
1373 win
= this.windows
[ win
];
1374 } else if ( !this.hasWindow( win
) ) {
1380 closing
.reject( new OO
.ui
.Error(
1381 'Cannot close window: window is not attached to manager'
1383 } else if ( win
!== this.currentWindow
) {
1384 closing
.reject( new OO
.ui
.Error(
1385 'Cannot close window: window already closed with different data'
1387 } else if ( this.preparingToClose
|| this.closing
) {
1388 closing
.reject( new OO
.ui
.Error(
1389 'Cannot close window: window already closing with different data'
1394 if ( closing
.state() !== 'rejected' ) {
1395 // If the window is currently opening, close it when it's done
1396 this.preparingToClose
= $.when( this.opening
);
1397 // Ensure handlers get called after preparingToClose is set
1398 this.preparingToClose
.always( function () {
1399 manager
.closing
= closing
;
1400 manager
.preparingToClose
= null;
1401 manager
.emit( 'closing', win
, closing
, data
);
1402 opened
= manager
.opened
;
1403 manager
.opened
= null;
1404 opened
.resolve( closing
.promise(), data
);
1405 setTimeout( function () {
1406 win
.hold( data
).then( function () {
1407 closing
.notify( { state
: 'hold' } );
1408 setTimeout( function () {
1409 win
.teardown( data
).then( function () {
1410 closing
.notify( { state
: 'teardown' } );
1411 if ( manager
.modal
) {
1412 manager
.toggleGlobalEvents( false );
1413 manager
.toggleAriaIsolation( false );
1415 manager
.closing
= null;
1416 manager
.currentWindow
= null;
1417 closing
.resolve( data
);
1419 }, manager
.getTeardownDelay() );
1421 }, manager
.getHoldDelay() );
1425 return closing
.promise();
1429 * Add windows to the window manager.
1431 * Windows can be added by reference, symbolic name, or explicitly defined symbolic names.
1432 * See the [OOjs ui documentation on MediaWiki] [2] for examples.
1433 * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
1435 * @param {Object.<string,OO.ui.Window>|OO.ui.Window[]} windows An array of window objects specified
1436 * by reference, symbolic name, or explicitly defined symbolic names.
1437 * @throws {Error} An error is thrown if a window is added by symbolic name, but has neither an
1438 * explicit nor a statically configured symbolic name.
1440 OO
.ui
.WindowManager
.prototype.addWindows = function ( windows
) {
1441 var i
, len
, win
, name
, list
;
1443 if ( Array
.isArray( windows
) ) {
1444 // Convert to map of windows by looking up symbolic names from static configuration
1446 for ( i
= 0, len
= windows
.length
; i
< len
; i
++ ) {
1447 name
= windows
[ i
].constructor.static.name
;
1448 if ( typeof name
!== 'string' ) {
1449 throw new Error( 'Cannot add window' );
1451 list
[ name
] = windows
[ i
];
1453 } else if ( OO
.isPlainObject( windows
) ) {
1458 for ( name
in list
) {
1460 this.windows
[ name
] = win
.toggle( false );
1461 this.$element
.append( win
.$element
);
1462 win
.setManager( this );
1467 * Remove the specified windows from the windows manager.
1469 * Windows will be closed before they are removed. If you wish to remove all windows, you may wish to use
1470 * the #clearWindows method instead. If you no longer need the window manager and want to ensure that it no
1471 * longer listens to events, use the #destroy method.
1473 * @param {string[]} names Symbolic names of windows to remove
1474 * @return {jQuery.Promise} Promise resolved when window is closed and removed
1475 * @throws {Error} An error is thrown if the named windows are not managed by the window manager.
1477 OO
.ui
.WindowManager
.prototype.removeWindows = function ( names
) {
1478 var i
, len
, win
, name
, cleanupWindow
,
1481 cleanup = function ( name
, win
) {
1482 delete manager
.windows
[ name
];
1483 win
.$element
.detach();
1486 for ( i
= 0, len
= names
.length
; i
< len
; i
++ ) {
1488 win
= this.windows
[ name
];
1490 throw new Error( 'Cannot remove window' );
1492 cleanupWindow
= cleanup
.bind( null, name
, win
);
1493 promises
.push( this.closeWindow( name
).then( cleanupWindow
, cleanupWindow
) );
1496 return $.when
.apply( $, promises
);
1500 * Remove all windows from the window manager.
1502 * Windows will be closed before they are removed. Note that the window manager, though not in use, will still
1503 * listen to events. If the window manager will not be used again, you may wish to use the #destroy method instead.
1504 * To remove just a subset of windows, use the #removeWindows method.
1506 * @return {jQuery.Promise} Promise resolved when all windows are closed and removed
1508 OO
.ui
.WindowManager
.prototype.clearWindows = function () {
1509 return this.removeWindows( Object
.keys( this.windows
) );
1513 * Set dialog size. In general, this method should not be called directly.
1515 * Fullscreen mode will be used if the dialog is too wide to fit in the screen.
1519 OO
.ui
.WindowManager
.prototype.updateWindowSize = function ( win
) {
1522 // Bypass for non-current, and thus invisible, windows
1523 if ( win
!== this.currentWindow
) {
1527 isFullscreen
= win
.getSize() === 'full';
1529 this.$element
.toggleClass( 'oo-ui-windowManager-fullscreen', isFullscreen
);
1530 this.$element
.toggleClass( 'oo-ui-windowManager-floating', !isFullscreen
);
1531 win
.setDimensions( win
.getSizeProperties() );
1533 this.emit( 'resize', win
);
1539 * Bind or unbind global events for scrolling.
1542 * @param {boolean} [on] Bind global events
1545 OO
.ui
.WindowManager
.prototype.toggleGlobalEvents = function ( on
) {
1546 var scrollWidth
, bodyMargin
,
1547 $body
= $( this.getElementDocument().body
),
1548 // We could have multiple window managers open so only modify
1549 // the body css at the bottom of the stack
1550 stackDepth
= $body
.data( 'windowManagerGlobalEvents' ) || 0;
1552 on
= on
=== undefined ? !!this.globalEvents
: !!on
;
1555 if ( !this.globalEvents
) {
1556 $( this.getElementWindow() ).on( {
1557 // Start listening for top-level window dimension changes
1558 'orientationchange resize': this.onWindowResizeHandler
1560 if ( stackDepth
=== 0 ) {
1561 scrollWidth
= window
.innerWidth
- document
.documentElement
.clientWidth
;
1562 bodyMargin
= parseFloat( $body
.css( 'margin-right' ) ) || 0;
1565 'margin-right': bodyMargin
+ scrollWidth
1569 this.globalEvents
= true;
1571 } else if ( this.globalEvents
) {
1572 $( this.getElementWindow() ).off( {
1573 // Stop listening for top-level window dimension changes
1574 'orientationchange resize': this.onWindowResizeHandler
1577 if ( stackDepth
=== 0 ) {
1583 this.globalEvents
= false;
1585 $body
.data( 'windowManagerGlobalEvents', stackDepth
);
1591 * Toggle screen reader visibility of content other than the window manager.
1594 * @param {boolean} [isolate] Make only the window manager visible to screen readers
1597 OO
.ui
.WindowManager
.prototype.toggleAriaIsolation = function ( isolate
) {
1598 isolate
= isolate
=== undefined ? !this.$ariaHidden
: !!isolate
;
1601 if ( !this.$ariaHidden
) {
1602 // Hide everything other than the window manager from screen readers
1603 this.$ariaHidden
= $( 'body' )
1605 .not( this.$element
.parentsUntil( 'body' ).last() )
1606 .attr( 'aria-hidden', '' );
1608 } else if ( this.$ariaHidden
) {
1609 // Restore screen reader visibility
1610 this.$ariaHidden
.removeAttr( 'aria-hidden' );
1611 this.$ariaHidden
= null;
1618 * Destroy the window manager.
1620 * Destroying the window manager ensures that it will no longer listen to events. If you would like to
1621 * continue using the window manager, but wish to remove all windows from it, use the #clearWindows method
1624 OO
.ui
.WindowManager
.prototype.destroy = function () {
1625 this.toggleGlobalEvents( false );
1626 this.toggleAriaIsolation( false );
1627 this.clearWindows();
1628 this.$element
.remove();
1632 * A window is a container for elements that are in a child frame. They are used with
1633 * a window manager (OO.ui.WindowManager), which is used to open and close the window and control
1634 * its presentation. The size of a window is specified using a symbolic name (e.g., ‘small’, ‘medium’,
1635 * ‘large’), which is interpreted by the window manager. If the requested size is not recognized,
1636 * the window manager will choose a sensible fallback.
1638 * The lifecycle of a window has three primary stages (opening, opened, and closing) in which
1639 * different processes are executed:
1641 * **opening**: The opening stage begins when the window manager's {@link OO.ui.WindowManager#openWindow
1642 * openWindow} or the window's {@link #open open} methods are used, and the window manager begins to open
1645 * - {@link #getSetupProcess} method is called and its result executed
1646 * - {@link #getReadyProcess} method is called and its result executed
1648 * **opened**: The window is now open
1650 * **closing**: The closing stage begins when the window manager's
1651 * {@link OO.ui.WindowManager#closeWindow closeWindow}
1652 * or the window's {@link #close} methods are used, and the window manager begins to close the window.
1654 * - {@link #getHoldProcess} method is called and its result executed
1655 * - {@link #getTeardownProcess} method is called and its result executed. The window is now closed
1657 * Each of the window's processes (setup, ready, hold, and teardown) can be extended in subclasses
1658 * by overriding the window's #getSetupProcess, #getReadyProcess, #getHoldProcess and #getTeardownProcess
1659 * methods. Note that each {@link OO.ui.Process process} is executed in series, so asynchronous
1660 * processing can complete. Always assume window processes are executed asynchronously.
1662 * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
1664 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows
1668 * @extends OO.ui.Element
1669 * @mixins OO.EventEmitter
1672 * @param {Object} [config] Configuration options
1673 * @cfg {string} [size] Symbolic name of the dialog size: `small`, `medium`, `large`, `larger` or
1674 * `full`. If omitted, the value of the {@link #static-size static size} property will be used.
1676 OO
.ui
.Window
= function OoUiWindow( config
) {
1677 // Configuration initialization
1678 config
= config
|| {};
1680 // Parent constructor
1681 OO
.ui
.Window
.parent
.call( this, config
);
1683 // Mixin constructors
1684 OO
.EventEmitter
.call( this );
1687 this.manager
= null;
1688 this.size
= config
.size
|| this.constructor.static.size
;
1689 this.$frame
= $( '<div>' );
1690 this.$overlay
= $( '<div>' );
1691 this.$content
= $( '<div>' );
1693 this.$focusTrapBefore
= $( '<div>' ).prop( 'tabIndex', 0 );
1694 this.$focusTrapAfter
= $( '<div>' ).prop( 'tabIndex', 0 );
1695 this.$focusTraps
= this.$focusTrapBefore
.add( this.$focusTrapAfter
);
1698 this.$overlay
.addClass( 'oo-ui-window-overlay' );
1700 .addClass( 'oo-ui-window-content' )
1701 .attr( 'tabindex', 0 );
1703 .addClass( 'oo-ui-window-frame' )
1704 .append( this.$focusTrapBefore
, this.$content
, this.$focusTrapAfter
);
1707 .addClass( 'oo-ui-window' )
1708 .append( this.$frame
, this.$overlay
);
1710 // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
1711 // that reference properties not initialized at that time of parent class construction
1712 // TODO: Find a better way to handle post-constructor setup
1713 this.visible
= false;
1714 this.$element
.addClass( 'oo-ui-element-hidden' );
1719 OO
.inheritClass( OO
.ui
.Window
, OO
.ui
.Element
);
1720 OO
.mixinClass( OO
.ui
.Window
, OO
.EventEmitter
);
1722 /* Static Properties */
1725 * Symbolic name of the window size: `small`, `medium`, `large`, `larger` or `full`.
1727 * The static size is used if no #size is configured during construction.
1731 * @property {string}
1733 OO
.ui
.Window
.static.size
= 'medium';
1738 * Handle mouse down events.
1741 * @param {jQuery.Event} e Mouse down event
1743 OO
.ui
.Window
.prototype.onMouseDown = function ( e
) {
1744 // Prevent clicking on the click-block from stealing focus
1745 if ( e
.target
=== this.$element
[ 0 ] ) {
1751 * Check if the window has been initialized.
1753 * Initialization occurs when a window is added to a manager.
1755 * @return {boolean} Window has been initialized
1757 OO
.ui
.Window
.prototype.isInitialized = function () {
1758 return !!this.manager
;
1762 * Check if the window is visible.
1764 * @return {boolean} Window is visible
1766 OO
.ui
.Window
.prototype.isVisible = function () {
1767 return this.visible
;
1771 * Check if the window is opening.
1773 * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpening isOpening}
1776 * @return {boolean} Window is opening
1778 OO
.ui
.Window
.prototype.isOpening = function () {
1779 return this.manager
.isOpening( this );
1783 * Check if the window is closing.
1785 * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isClosing isClosing} method.
1787 * @return {boolean} Window is closing
1789 OO
.ui
.Window
.prototype.isClosing = function () {
1790 return this.manager
.isClosing( this );
1794 * Check if the window is opened.
1796 * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpened isOpened} method.
1798 * @return {boolean} Window is opened
1800 OO
.ui
.Window
.prototype.isOpened = function () {
1801 return this.manager
.isOpened( this );
1805 * Get the window manager.
1807 * All windows must be attached to a window manager, which is used to open
1808 * and close the window and control its presentation.
1810 * @return {OO.ui.WindowManager} Manager of window
1812 OO
.ui
.Window
.prototype.getManager = function () {
1813 return this.manager
;
1817 * Get the symbolic name of the window size (e.g., `small` or `medium`).
1819 * @return {string} Symbolic name of the size: `small`, `medium`, `large`, `larger`, `full`
1821 OO
.ui
.Window
.prototype.getSize = function () {
1822 var viewport
= OO
.ui
.Element
.static.getDimensions( this.getElementWindow() ),
1823 sizes
= this.manager
.constructor.static.sizes
,
1826 if ( !sizes
[ size
] ) {
1827 size
= this.manager
.constructor.static.defaultSize
;
1829 if ( size
!== 'full' && viewport
.rect
.right
- viewport
.rect
.left
< sizes
[ size
].width
) {
1837 * Get the size properties associated with the current window size
1839 * @return {Object} Size properties
1841 OO
.ui
.Window
.prototype.getSizeProperties = function () {
1842 return this.manager
.constructor.static.sizes
[ this.getSize() ];
1846 * Disable transitions on window's frame for the duration of the callback function, then enable them
1850 * @param {Function} callback Function to call while transitions are disabled
1852 OO
.ui
.Window
.prototype.withoutSizeTransitions = function ( callback
) {
1853 // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.
1854 // Disable transitions first, otherwise we'll get values from when the window was animating.
1856 styleObj
= this.$frame
[ 0 ].style
;
1857 oldTransition
= styleObj
.transition
|| styleObj
.OTransition
|| styleObj
.MsTransition
||
1858 styleObj
.MozTransition
|| styleObj
.WebkitTransition
;
1859 styleObj
.transition
= styleObj
.OTransition
= styleObj
.MsTransition
=
1860 styleObj
.MozTransition
= styleObj
.WebkitTransition
= 'none';
1862 // Force reflow to make sure the style changes done inside callback really are not transitioned
1863 this.$frame
.height();
1864 styleObj
.transition
= styleObj
.OTransition
= styleObj
.MsTransition
=
1865 styleObj
.MozTransition
= styleObj
.WebkitTransition
= oldTransition
;
1869 * Get the height of the full window contents (i.e., the window head, body and foot together).
1871 * What consistitutes the head, body, and foot varies depending on the window type.
1872 * A {@link OO.ui.MessageDialog message dialog} displays a title and message in its body,
1873 * and any actions in the foot. A {@link OO.ui.ProcessDialog process dialog} displays a title
1874 * and special actions in the head, and dialog content in the body.
1876 * To get just the height of the dialog body, use the #getBodyHeight method.
1878 * @return {number} The height of the window contents (the dialog head, body and foot) in pixels
1880 OO
.ui
.Window
.prototype.getContentHeight = function () {
1883 bodyStyleObj
= this.$body
[ 0 ].style
,
1884 frameStyleObj
= this.$frame
[ 0 ].style
;
1886 // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.
1887 // Disable transitions first, otherwise we'll get values from when the window was animating.
1888 this.withoutSizeTransitions( function () {
1889 var oldHeight
= frameStyleObj
.height
,
1890 oldPosition
= bodyStyleObj
.position
;
1891 frameStyleObj
.height
= '1px';
1892 // Force body to resize to new width
1893 bodyStyleObj
.position
= 'relative';
1894 bodyHeight
= win
.getBodyHeight();
1895 frameStyleObj
.height
= oldHeight
;
1896 bodyStyleObj
.position
= oldPosition
;
1900 // Add buffer for border
1901 ( this.$frame
.outerHeight() - this.$frame
.innerHeight() ) +
1902 // Use combined heights of children
1903 ( this.$head
.outerHeight( true ) + bodyHeight
+ this.$foot
.outerHeight( true ) )
1908 * Get the height of the window body.
1910 * To get the height of the full window contents (the window body, head, and foot together),
1911 * use #getContentHeight.
1913 * When this function is called, the window will temporarily have been resized
1914 * to height=1px, so .scrollHeight measurements can be taken accurately.
1916 * @return {number} Height of the window body in pixels
1918 OO
.ui
.Window
.prototype.getBodyHeight = function () {
1919 return this.$body
[ 0 ].scrollHeight
;
1923 * Get the directionality of the frame (right-to-left or left-to-right).
1925 * @return {string} Directionality: `'ltr'` or `'rtl'`
1927 OO
.ui
.Window
.prototype.getDir = function () {
1928 return OO
.ui
.Element
.static.getDir( this.$content
) || 'ltr';
1932 * Get the 'setup' process.
1934 * The setup process is used to set up a window for use in a particular context,
1935 * based on the `data` argument. This method is called during the opening phase of the window’s
1938 * Override this method to add additional steps to the ‘setup’ process the parent method provides
1939 * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
1942 * To add window content that persists between openings, you may wish to use the #initialize method
1945 * @param {Object} [data] Window opening data
1946 * @return {OO.ui.Process} Setup process
1948 OO
.ui
.Window
.prototype.getSetupProcess = function () {
1949 return new OO
.ui
.Process();
1953 * Get the ‘ready’ process.
1955 * The ready process is used to ready a window for use in a particular
1956 * context, based on the `data` argument. This method is called during the opening phase of
1957 * the window’s lifecycle, after the window has been {@link #getSetupProcess setup}.
1959 * Override this method to add additional steps to the ‘ready’ process the parent method
1960 * provides using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next}
1961 * methods of OO.ui.Process.
1963 * @param {Object} [data] Window opening data
1964 * @return {OO.ui.Process} Ready process
1966 OO
.ui
.Window
.prototype.getReadyProcess = function () {
1967 return new OO
.ui
.Process();
1971 * Get the 'hold' process.
1973 * The hold proccess is used to keep a window from being used in a particular context,
1974 * based on the `data` argument. This method is called during the closing phase of the window’s
1977 * Override this method to add additional steps to the 'hold' process the parent method provides
1978 * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
1981 * @param {Object} [data] Window closing data
1982 * @return {OO.ui.Process} Hold process
1984 OO
.ui
.Window
.prototype.getHoldProcess = function () {
1985 return new OO
.ui
.Process();
1989 * Get the ‘teardown’ process.
1991 * The teardown process is used to teardown a window after use. During teardown,
1992 * user interactions within the window are conveyed and the window is closed, based on the `data`
1993 * argument. This method is called during the closing phase of the window’s lifecycle.
1995 * Override this method to add additional steps to the ‘teardown’ process the parent method provides
1996 * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
1999 * @param {Object} [data] Window closing data
2000 * @return {OO.ui.Process} Teardown process
2002 OO
.ui
.Window
.prototype.getTeardownProcess = function () {
2003 return new OO
.ui
.Process();
2007 * Set the window manager.
2009 * This will cause the window to initialize. Calling it more than once will cause an error.
2011 * @param {OO.ui.WindowManager} manager Manager for this window
2012 * @throws {Error} An error is thrown if the method is called more than once
2015 OO
.ui
.Window
.prototype.setManager = function ( manager
) {
2016 if ( this.manager
) {
2017 throw new Error( 'Cannot set window manager, window already has a manager' );
2020 this.manager
= manager
;
2027 * Set the window size by symbolic name (e.g., 'small' or 'medium')
2029 * @param {string} size Symbolic name of size: `small`, `medium`, `large`, `larger` or
2033 OO
.ui
.Window
.prototype.setSize = function ( size
) {
2040 * Update the window size.
2042 * @throws {Error} An error is thrown if the window is not attached to a window manager
2045 OO
.ui
.Window
.prototype.updateSize = function () {
2046 if ( !this.manager
) {
2047 throw new Error( 'Cannot update window size, must be attached to a manager' );
2050 this.manager
.updateWindowSize( this );
2056 * Set window dimensions. This method is called by the {@link OO.ui.WindowManager window manager}
2057 * when the window is opening. In general, setDimensions should not be called directly.
2059 * To set the size of the window, use the #setSize method.
2061 * @param {Object} dim CSS dimension properties
2062 * @param {string|number} [dim.width] Width
2063 * @param {string|number} [dim.minWidth] Minimum width
2064 * @param {string|number} [dim.maxWidth] Maximum width
2065 * @param {string|number} [dim.height] Height, omit to set based on height of contents
2066 * @param {string|number} [dim.minHeight] Minimum height
2067 * @param {string|number} [dim.maxHeight] Maximum height
2070 OO
.ui
.Window
.prototype.setDimensions = function ( dim
) {
2073 styleObj
= this.$frame
[ 0 ].style
;
2075 // Calculate the height we need to set using the correct width
2076 if ( dim
.height
=== undefined ) {
2077 this.withoutSizeTransitions( function () {
2078 var oldWidth
= styleObj
.width
;
2079 win
.$frame
.css( 'width', dim
.width
|| '' );
2080 height
= win
.getContentHeight();
2081 styleObj
.width
= oldWidth
;
2084 height
= dim
.height
;
2088 width
: dim
.width
|| '',
2089 minWidth
: dim
.minWidth
|| '',
2090 maxWidth
: dim
.maxWidth
|| '',
2091 height
: height
|| '',
2092 minHeight
: dim
.minHeight
|| '',
2093 maxHeight
: dim
.maxHeight
|| ''
2100 * Initialize window contents.
2102 * Before the window is opened for the first time, #initialize is called so that content that
2103 * persists between openings can be added to the window.
2105 * To set up a window with new content each time the window opens, use #getSetupProcess.
2107 * @throws {Error} An error is thrown if the window is not attached to a window manager
2110 OO
.ui
.Window
.prototype.initialize = function () {
2111 if ( !this.manager
) {
2112 throw new Error( 'Cannot initialize window, must be attached to a manager' );
2116 this.$head
= $( '<div>' );
2117 this.$body
= $( '<div>' );
2118 this.$foot
= $( '<div>' );
2119 this.$document
= $( this.getElementDocument() );
2122 this.$element
.on( 'mousedown', this.onMouseDown
.bind( this ) );
2125 this.$head
.addClass( 'oo-ui-window-head' );
2126 this.$body
.addClass( 'oo-ui-window-body' );
2127 this.$foot
.addClass( 'oo-ui-window-foot' );
2128 this.$content
.append( this.$head
, this.$body
, this.$foot
);
2134 * Called when someone tries to focus the hidden element at the end of the dialog.
2135 * Sends focus back to the start of the dialog.
2137 * @param {jQuery.Event} event Focus event
2139 OO
.ui
.Window
.prototype.onFocusTrapFocused = function ( event
) {
2140 var backwards
= this.$focusTrapBefore
.is( event
.target
),
2141 element
= OO
.ui
.findFocusable( this.$content
, backwards
);
2143 // There's a focusable element inside the content, at the front or
2144 // back depending on which focus trap we hit; select it.
2147 // There's nothing focusable inside the content. As a fallback,
2148 // this.$content is focusable, and focusing it will keep our focus
2149 // properly trapped. It's not a *meaningful* focus, since it's just
2150 // the content-div for the Window, but it's better than letting focus
2151 // escape into the page.
2152 this.$content
.focus();
2159 * This method is a wrapper around a call to the window manager’s {@link OO.ui.WindowManager#openWindow openWindow}
2160 * method, which returns a promise resolved when the window is done opening.
2162 * To customize the window each time it opens, use #getSetupProcess or #getReadyProcess.
2164 * @param {Object} [data] Window opening data
2165 * @return {jQuery.Promise} Promise resolved with a value when the window is opened, or rejected
2166 * if the window fails to open. When the promise is resolved successfully, the first argument of the
2167 * value is a new promise, which is resolved when the window begins closing.
2168 * @throws {Error} An error is thrown if the window is not attached to a window manager
2170 OO
.ui
.Window
.prototype.open = function ( data
) {
2171 if ( !this.manager
) {
2172 throw new Error( 'Cannot open window, must be attached to a manager' );
2175 return this.manager
.openWindow( this, data
);
2181 * This method is a wrapper around a call to the window
2182 * manager’s {@link OO.ui.WindowManager#closeWindow closeWindow} method,
2183 * which returns a closing promise resolved when the window is done closing.
2185 * The window's #getHoldProcess and #getTeardownProcess methods are called during the closing
2186 * phase of the window’s lifecycle and can be used to specify closing behavior each time
2187 * the window closes.
2189 * @param {Object} [data] Window closing data
2190 * @return {jQuery.Promise} Promise resolved when window is closed
2191 * @throws {Error} An error is thrown if the window is not attached to a window manager
2193 OO
.ui
.Window
.prototype.close = function ( data
) {
2194 if ( !this.manager
) {
2195 throw new Error( 'Cannot close window, must be attached to a manager' );
2198 return this.manager
.closeWindow( this, data
);
2204 * This is called by OO.ui.WindowManager during window opening, and should not be called directly
2207 * @param {Object} [data] Window opening data
2208 * @return {jQuery.Promise} Promise resolved when window is setup
2210 OO
.ui
.Window
.prototype.setup = function ( data
) {
2213 this.toggle( true );
2215 this.focusTrapHandler
= OO
.ui
.bind( this.onFocusTrapFocused
, this );
2216 this.$focusTraps
.on( 'focus', this.focusTrapHandler
);
2218 return this.getSetupProcess( data
).execute().then( function () {
2219 // Force redraw by asking the browser to measure the elements' widths
2220 win
.$element
.addClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
2221 win
.$content
.addClass( 'oo-ui-window-content-setup' ).width();
2228 * This is called by OO.ui.WindowManager during window opening, and should not be called directly
2231 * @param {Object} [data] Window opening data
2232 * @return {jQuery.Promise} Promise resolved when window is ready
2234 OO
.ui
.Window
.prototype.ready = function ( data
) {
2237 this.$content
.focus();
2238 return this.getReadyProcess( data
).execute().then( function () {
2239 // Force redraw by asking the browser to measure the elements' widths
2240 win
.$element
.addClass( 'oo-ui-window-ready' ).width();
2241 win
.$content
.addClass( 'oo-ui-window-content-ready' ).width();
2248 * This is called by OO.ui.WindowManager during window closing, and should not be called directly
2251 * @param {Object} [data] Window closing data
2252 * @return {jQuery.Promise} Promise resolved when window is held
2254 OO
.ui
.Window
.prototype.hold = function ( data
) {
2257 return this.getHoldProcess( data
).execute().then( function () {
2258 // Get the focused element within the window's content
2259 var $focus
= win
.$content
.find( OO
.ui
.Element
.static.getDocument( win
.$content
).activeElement
);
2261 // Blur the focused element
2262 if ( $focus
.length
) {
2266 // Force redraw by asking the browser to measure the elements' widths
2267 win
.$element
.removeClass( 'oo-ui-window-ready' ).width();
2268 win
.$content
.removeClass( 'oo-ui-window-content-ready' ).width();
2275 * This is called by OO.ui.WindowManager during window closing, and should not be called directly
2278 * @param {Object} [data] Window closing data
2279 * @return {jQuery.Promise} Promise resolved when window is torn down
2281 OO
.ui
.Window
.prototype.teardown = function ( data
) {
2284 return this.getTeardownProcess( data
).execute().then( function () {
2285 // Force redraw by asking the browser to measure the elements' widths
2286 win
.$element
.removeClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
2287 win
.$content
.removeClass( 'oo-ui-window-content-setup' ).width();
2288 win
.$focusTraps
.off( 'focus', win
.focusTrapHandler
);
2289 win
.toggle( false );
2294 * The Dialog class serves as the base class for the other types of dialogs.
2295 * Unless extended to include controls, the rendered dialog box is a simple window
2296 * that users can close by hitting the ‘Esc’ key. Dialog windows are used with OO.ui.WindowManager,
2297 * which opens, closes, and controls the presentation of the window. See the
2298 * [OOjs UI documentation on MediaWiki] [1] for more information.
2301 * // A simple dialog window.
2302 * function MyDialog( config ) {
2303 * MyDialog.parent.call( this, config );
2305 * OO.inheritClass( MyDialog, OO.ui.Dialog );
2306 * MyDialog.prototype.initialize = function () {
2307 * MyDialog.parent.prototype.initialize.call( this );
2308 * this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
2309 * this.content.$element.append( '<p>A simple dialog window. Press \'Esc\' to close.</p>' );
2310 * this.$body.append( this.content.$element );
2312 * MyDialog.prototype.getBodyHeight = function () {
2313 * return this.content.$element.outerHeight( true );
2315 * var myDialog = new MyDialog( {
2318 * // Create and append a window manager, which opens and closes the window.
2319 * var windowManager = new OO.ui.WindowManager();
2320 * $( 'body' ).append( windowManager.$element );
2321 * windowManager.addWindows( [ myDialog ] );
2322 * // Open the window!
2323 * windowManager.openWindow( myDialog );
2325 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Dialogs
2329 * @extends OO.ui.Window
2330 * @mixins OO.ui.mixin.PendingElement
2333 * @param {Object} [config] Configuration options
2335 OO
.ui
.Dialog
= function OoUiDialog( config
) {
2336 // Parent constructor
2337 OO
.ui
.Dialog
.parent
.call( this, config
);
2339 // Mixin constructors
2340 OO
.ui
.mixin
.PendingElement
.call( this );
2343 this.actions
= new OO
.ui
.ActionSet();
2344 this.attachedActions
= [];
2345 this.currentAction
= null;
2346 this.onDialogKeyDownHandler
= this.onDialogKeyDown
.bind( this );
2349 this.actions
.connect( this, {
2350 click
: 'onActionClick',
2351 resize
: 'onActionResize',
2352 change
: 'onActionsChange'
2357 .addClass( 'oo-ui-dialog' )
2358 .attr( 'role', 'dialog' );
2363 OO
.inheritClass( OO
.ui
.Dialog
, OO
.ui
.Window
);
2364 OO
.mixinClass( OO
.ui
.Dialog
, OO
.ui
.mixin
.PendingElement
);
2366 /* Static Properties */
2369 * Symbolic name of dialog.
2371 * The dialog class must have a symbolic name in order to be registered with OO.Factory.
2372 * Please see the [OOjs UI documentation on MediaWiki] [3] for more information.
2374 * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
2379 * @property {string}
2381 OO
.ui
.Dialog
.static.name
= '';
2386 * The title can be specified as a plaintext string, a {@link OO.ui.mixin.LabelElement Label} node, or a function
2387 * that will produce a Label node or string. The title can also be specified with data passed to the
2388 * constructor (see #getSetupProcess). In this case, the static value will be overridden.
2393 * @property {jQuery|string|Function}
2395 OO
.ui
.Dialog
.static.title
= '';
2398 * An array of configured {@link OO.ui.ActionWidget action widgets}.
2400 * Actions can also be specified with data passed to the constructor (see #getSetupProcess). In this case, the static
2401 * value will be overridden.
2403 * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
2407 * @property {Object[]}
2409 OO
.ui
.Dialog
.static.actions
= [];
2412 * Close the dialog when the 'Esc' key is pressed.
2417 * @property {boolean}
2419 OO
.ui
.Dialog
.static.escapable
= true;
2424 * Handle frame document key down events.
2427 * @param {jQuery.Event} e Key down event
2429 OO
.ui
.Dialog
.prototype.onDialogKeyDown = function ( e
) {
2431 if ( e
.which
=== OO
.ui
.Keys
.ESCAPE
&& this.constructor.static.escapable
) {
2432 this.executeAction( '' );
2434 e
.stopPropagation();
2435 } else if ( e
.which
=== OO
.ui
.Keys
.ENTER
&& e
.ctrlKey
) {
2436 actions
= this.actions
.get( { flags
: 'primary', visible
: true, disabled
: false } );
2437 if ( actions
.length
> 0 ) {
2438 this.executeAction( actions
[ 0 ].getAction() );
2440 e
.stopPropagation();
2446 * Handle action resized events.
2449 * @param {OO.ui.ActionWidget} action Action that was resized
2451 OO
.ui
.Dialog
.prototype.onActionResize = function () {
2452 // Override in subclass
2456 * Handle action click events.
2459 * @param {OO.ui.ActionWidget} action Action that was clicked
2461 OO
.ui
.Dialog
.prototype.onActionClick = function ( action
) {
2462 if ( !this.isPending() ) {
2463 this.executeAction( action
.getAction() );
2468 * Handle actions change event.
2472 OO
.ui
.Dialog
.prototype.onActionsChange = function () {
2473 this.detachActions();
2474 if ( !this.isClosing() ) {
2475 this.attachActions();
2480 * Get the set of actions used by the dialog.
2482 * @return {OO.ui.ActionSet}
2484 OO
.ui
.Dialog
.prototype.getActions = function () {
2485 return this.actions
;
2489 * Get a process for taking action.
2491 * When you override this method, you can create a new OO.ui.Process and return it, or add additional
2492 * accept steps to the process the parent method provides using the {@link OO.ui.Process#first 'first'}
2493 * and {@link OO.ui.Process#next 'next'} methods of OO.ui.Process.
2495 * @param {string} [action] Symbolic name of action
2496 * @return {OO.ui.Process} Action process
2498 OO
.ui
.Dialog
.prototype.getActionProcess = function ( action
) {
2499 return new OO
.ui
.Process()
2500 .next( function () {
2502 // An empty action always closes the dialog without data, which should always be
2503 // safe and make no changes
2512 * @param {Object} [data] Dialog opening data
2513 * @param {jQuery|string|Function|null} [data.title] Dialog title, omit to use
2514 * the {@link #static-title static title}
2515 * @param {Object[]} [data.actions] List of configuration options for each
2516 * {@link OO.ui.ActionWidget action widget}, omit to use {@link #static-actions static actions}.
2518 OO
.ui
.Dialog
.prototype.getSetupProcess = function ( data
) {
2522 return OO
.ui
.Dialog
.parent
.prototype.getSetupProcess
.call( this, data
)
2523 .next( function () {
2524 var config
= this.constructor.static,
2525 actions
= data
.actions
!== undefined ? data
.actions
: config
.actions
,
2526 title
= data
.title
!== undefined ? data
.title
: config
.title
;
2528 this.title
.setLabel( title
).setTitle( title
);
2529 this.actions
.add( this.getActionWidgets( actions
) );
2531 this.$element
.on( 'keydown', this.onDialogKeyDownHandler
);
2538 OO
.ui
.Dialog
.prototype.getTeardownProcess = function ( data
) {
2540 return OO
.ui
.Dialog
.parent
.prototype.getTeardownProcess
.call( this, data
)
2541 .first( function () {
2542 this.$element
.off( 'keydown', this.onDialogKeyDownHandler
);
2544 this.actions
.clear();
2545 this.currentAction
= null;
2552 OO
.ui
.Dialog
.prototype.initialize = function () {
2556 OO
.ui
.Dialog
.parent
.prototype.initialize
.call( this );
2558 titleId
= OO
.ui
.generateElementId();
2561 this.title
= new OO
.ui
.LabelWidget( {
2566 this.$content
.addClass( 'oo-ui-dialog-content' );
2567 this.$element
.attr( 'aria-labelledby', titleId
);
2568 this.setPendingElement( this.$head
);
2572 * Get action widgets from a list of configs
2574 * @param {Object[]} actions Action widget configs
2575 * @return {OO.ui.ActionWidget[]} Action widgets
2577 OO
.ui
.Dialog
.prototype.getActionWidgets = function ( actions
) {
2578 var i
, len
, widgets
= [];
2579 for ( i
= 0, len
= actions
.length
; i
< len
; i
++ ) {
2581 new OO
.ui
.ActionWidget( actions
[ i
] )
2588 * Attach action actions.
2592 OO
.ui
.Dialog
.prototype.attachActions = function () {
2593 // Remember the list of potentially attached actions
2594 this.attachedActions
= this.actions
.get();
2598 * Detach action actions.
2603 OO
.ui
.Dialog
.prototype.detachActions = function () {
2606 // Detach all actions that may have been previously attached
2607 for ( i
= 0, len
= this.attachedActions
.length
; i
< len
; i
++ ) {
2608 this.attachedActions
[ i
].$element
.detach();
2610 this.attachedActions
= [];
2614 * Execute an action.
2616 * @param {string} action Symbolic name of action to execute
2617 * @return {jQuery.Promise} Promise resolved when action completes, rejected if it fails
2619 OO
.ui
.Dialog
.prototype.executeAction = function ( action
) {
2621 this.currentAction
= action
;
2622 return this.getActionProcess( action
).execute()
2623 .always( this.popPending
.bind( this ) );
2627 * MessageDialogs display a confirmation or alert message. By default, the rendered dialog box
2628 * consists of a header that contains the dialog title, a body with the message, and a footer that
2629 * contains any {@link OO.ui.ActionWidget action widgets}. The MessageDialog class is the only type
2630 * of {@link OO.ui.Dialog dialog} that is usually instantiated directly.
2632 * There are two basic types of message dialogs, confirmation and alert:
2634 * - **confirmation**: the dialog title describes what a progressive action will do and the message provides
2635 * more details about the consequences.
2636 * - **alert**: the dialog title describes which event occurred and the message provides more information
2637 * about why the event occurred.
2639 * The MessageDialog class specifies two actions: ‘accept’, the primary
2640 * action (e.g., ‘ok’) and ‘reject,’ the safe action (e.g., ‘cancel’). Both will close the window,
2641 * passing along the selected action.
2643 * For more information and examples, please see the [OOjs UI documentation on MediaWiki][1].
2646 * // Example: Creating and opening a message dialog window.
2647 * var messageDialog = new OO.ui.MessageDialog();
2649 * // Create and append a window manager.
2650 * var windowManager = new OO.ui.WindowManager();
2651 * $( 'body' ).append( windowManager.$element );
2652 * windowManager.addWindows( [ messageDialog ] );
2653 * // Open the window.
2654 * windowManager.openWindow( messageDialog, {
2655 * title: 'Basic message dialog',
2656 * message: 'This is the message'
2659 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Message_Dialogs
2662 * @extends OO.ui.Dialog
2665 * @param {Object} [config] Configuration options
2667 OO
.ui
.MessageDialog
= function OoUiMessageDialog( config
) {
2668 // Parent constructor
2669 OO
.ui
.MessageDialog
.parent
.call( this, config
);
2672 this.verticalActionLayout
= null;
2675 this.$element
.addClass( 'oo-ui-messageDialog' );
2680 OO
.inheritClass( OO
.ui
.MessageDialog
, OO
.ui
.Dialog
);
2682 /* Static Properties */
2684 OO
.ui
.MessageDialog
.static.name
= 'message';
2686 OO
.ui
.MessageDialog
.static.size
= 'small';
2688 OO
.ui
.MessageDialog
.static.verbose
= false;
2693 * The title of a confirmation dialog describes what a progressive action will do. The
2694 * title of an alert dialog describes which event occurred.
2698 * @property {jQuery|string|Function|null}
2700 OO
.ui
.MessageDialog
.static.title
= null;
2703 * The message displayed in the dialog body.
2705 * A confirmation message describes the consequences of a progressive action. An alert
2706 * message describes why an event occurred.
2710 * @property {jQuery|string|Function|null}
2712 OO
.ui
.MessageDialog
.static.message
= null;
2714 // Note that OO.ui.alert() and OO.ui.confirm() rely on these.
2715 OO
.ui
.MessageDialog
.static.actions
= [
2716 { action
: 'accept', label
: OO
.ui
.deferMsg( 'ooui-dialog-message-accept' ), flags
: 'primary' },
2717 { action
: 'reject', label
: OO
.ui
.deferMsg( 'ooui-dialog-message-reject' ), flags
: 'safe' }
2725 OO
.ui
.MessageDialog
.prototype.setManager = function ( manager
) {
2726 OO
.ui
.MessageDialog
.parent
.prototype.setManager
.call( this, manager
);
2729 this.manager
.connect( this, {
2739 OO
.ui
.MessageDialog
.prototype.onActionResize = function ( action
) {
2741 return OO
.ui
.MessageDialog
.parent
.prototype.onActionResize
.call( this, action
);
2745 * Handle window resized events.
2749 OO
.ui
.MessageDialog
.prototype.onResize = function () {
2751 dialog
.fitActions();
2752 // Wait for CSS transition to finish and do it again :(
2753 setTimeout( function () {
2754 dialog
.fitActions();
2759 * Toggle action layout between vertical and horizontal.
2762 * @param {boolean} [value] Layout actions vertically, omit to toggle
2765 OO
.ui
.MessageDialog
.prototype.toggleVerticalActionLayout = function ( value
) {
2766 value
= value
=== undefined ? !this.verticalActionLayout
: !!value
;
2768 if ( value
!== this.verticalActionLayout
) {
2769 this.verticalActionLayout
= value
;
2771 .toggleClass( 'oo-ui-messageDialog-actions-vertical', value
)
2772 .toggleClass( 'oo-ui-messageDialog-actions-horizontal', !value
);
2781 OO
.ui
.MessageDialog
.prototype.getActionProcess = function ( action
) {
2783 return new OO
.ui
.Process( function () {
2784 this.close( { action
: action
} );
2787 return OO
.ui
.MessageDialog
.parent
.prototype.getActionProcess
.call( this, action
);
2793 * @param {Object} [data] Dialog opening data
2794 * @param {jQuery|string|Function|null} [data.title] Description of the action being confirmed
2795 * @param {jQuery|string|Function|null} [data.message] Description of the action's consequence
2796 * @param {boolean} [data.verbose] Message is verbose and should be styled as a long message
2797 * @param {Object[]} [data.actions] List of OO.ui.ActionOptionWidget configuration options for each
2800 OO
.ui
.MessageDialog
.prototype.getSetupProcess = function ( data
) {
2804 return OO
.ui
.MessageDialog
.parent
.prototype.getSetupProcess
.call( this, data
)
2805 .next( function () {
2806 this.title
.setLabel(
2807 data
.title
!== undefined ? data
.title
: this.constructor.static.title
2809 this.message
.setLabel(
2810 data
.message
!== undefined ? data
.message
: this.constructor.static.message
2812 this.message
.$element
.toggleClass(
2813 'oo-ui-messageDialog-message-verbose',
2814 data
.verbose
!== undefined ? data
.verbose
: this.constructor.static.verbose
2822 OO
.ui
.MessageDialog
.prototype.getReadyProcess = function ( data
) {
2826 return OO
.ui
.MessageDialog
.parent
.prototype.getReadyProcess
.call( this, data
)
2827 .next( function () {
2828 // Focus the primary action button
2829 var actions
= this.actions
.get();
2830 actions
= actions
.filter( function ( action
) {
2831 return action
.getFlags().indexOf( 'primary' ) > -1;
2833 if ( actions
.length
> 0 ) {
2834 actions
[ 0 ].$button
.focus();
2842 OO
.ui
.MessageDialog
.prototype.getBodyHeight = function () {
2843 var bodyHeight
, oldOverflow
,
2844 $scrollable
= this.container
.$element
;
2846 oldOverflow
= $scrollable
[ 0 ].style
.overflow
;
2847 $scrollable
[ 0 ].style
.overflow
= 'hidden';
2849 OO
.ui
.Element
.static.reconsiderScrollbars( $scrollable
[ 0 ] );
2851 bodyHeight
= this.text
.$element
.outerHeight( true );
2852 $scrollable
[ 0 ].style
.overflow
= oldOverflow
;
2860 OO
.ui
.MessageDialog
.prototype.setDimensions = function ( dim
) {
2861 var $scrollable
= this.container
.$element
;
2862 OO
.ui
.MessageDialog
.parent
.prototype.setDimensions
.call( this, dim
);
2864 // Twiddle the overflow property, otherwise an unnecessary scrollbar will be produced.
2865 // Need to do it after transition completes (250ms), add 50ms just in case.
2866 setTimeout( function () {
2867 var oldOverflow
= $scrollable
[ 0 ].style
.overflow
;
2868 $scrollable
[ 0 ].style
.overflow
= 'hidden';
2870 OO
.ui
.Element
.static.reconsiderScrollbars( $scrollable
[ 0 ] );
2872 $scrollable
[ 0 ].style
.overflow
= oldOverflow
;
2881 OO
.ui
.MessageDialog
.prototype.initialize = function () {
2883 OO
.ui
.MessageDialog
.parent
.prototype.initialize
.call( this );
2886 this.$actions
= $( '<div>' );
2887 this.container
= new OO
.ui
.PanelLayout( {
2888 scrollable
: true, classes
: [ 'oo-ui-messageDialog-container' ]
2890 this.text
= new OO
.ui
.PanelLayout( {
2891 padded
: true, expanded
: false, classes
: [ 'oo-ui-messageDialog-text' ]
2893 this.message
= new OO
.ui
.LabelWidget( {
2894 classes
: [ 'oo-ui-messageDialog-message' ]
2898 this.title
.$element
.addClass( 'oo-ui-messageDialog-title' );
2899 this.$content
.addClass( 'oo-ui-messageDialog-content' );
2900 this.container
.$element
.append( this.text
.$element
);
2901 this.text
.$element
.append( this.title
.$element
, this.message
.$element
);
2902 this.$body
.append( this.container
.$element
);
2903 this.$actions
.addClass( 'oo-ui-messageDialog-actions' );
2904 this.$foot
.append( this.$actions
);
2910 OO
.ui
.MessageDialog
.prototype.attachActions = function () {
2911 var i
, len
, other
, special
, others
;
2914 OO
.ui
.MessageDialog
.parent
.prototype.attachActions
.call( this );
2916 special
= this.actions
.getSpecial();
2917 others
= this.actions
.getOthers();
2919 if ( special
.safe
) {
2920 this.$actions
.append( special
.safe
.$element
);
2921 special
.safe
.toggleFramed( false );
2923 if ( others
.length
) {
2924 for ( i
= 0, len
= others
.length
; i
< len
; i
++ ) {
2925 other
= others
[ i
];
2926 this.$actions
.append( other
.$element
);
2927 other
.toggleFramed( false );
2930 if ( special
.primary
) {
2931 this.$actions
.append( special
.primary
.$element
);
2932 special
.primary
.toggleFramed( false );
2935 if ( !this.isOpening() ) {
2936 // If the dialog is currently opening, this will be called automatically soon.
2937 // This also calls #fitActions.
2943 * Fit action actions into columns or rows.
2945 * Columns will be used if all labels can fit without overflow, otherwise rows will be used.
2949 OO
.ui
.MessageDialog
.prototype.fitActions = function () {
2951 previous
= this.verticalActionLayout
,
2952 actions
= this.actions
.get();
2955 this.toggleVerticalActionLayout( false );
2956 for ( i
= 0, len
= actions
.length
; i
< len
; i
++ ) {
2957 action
= actions
[ i
];
2958 if ( action
.$element
.innerWidth() < action
.$label
.outerWidth( true ) ) {
2959 this.toggleVerticalActionLayout( true );
2964 // Move the body out of the way of the foot
2965 this.$body
.css( 'bottom', this.$foot
.outerHeight( true ) );
2967 if ( this.verticalActionLayout
!== previous
) {
2968 // We changed the layout, window height might need to be updated.
2974 * ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary
2975 * to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error
2976 * interface} alerts users to the trouble, permitting the user to dismiss the error and try again when
2977 * relevant. The ProcessDialog class is always extended and customized with the actions and content
2978 * required for each process.
2980 * The process dialog box consists of a header that visually represents the ‘working’ state of long
2981 * processes with an animation. The header contains the dialog title as well as
2982 * two {@link OO.ui.ActionWidget action widgets}: a ‘safe’ action on the left (e.g., ‘Cancel’) and
2983 * a ‘primary’ action on the right (e.g., ‘Done’).
2985 * Like other windows, the process dialog is managed by a {@link OO.ui.WindowManager window manager}.
2986 * Please see the [OOjs UI documentation on MediaWiki][1] for more information and examples.
2989 * // Example: Creating and opening a process dialog window.
2990 * function MyProcessDialog( config ) {
2991 * MyProcessDialog.parent.call( this, config );
2993 * OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
2995 * MyProcessDialog.static.title = 'Process dialog';
2996 * MyProcessDialog.static.actions = [
2997 * { action: 'save', label: 'Done', flags: 'primary' },
2998 * { label: 'Cancel', flags: 'safe' }
3001 * MyProcessDialog.prototype.initialize = function () {
3002 * MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
3003 * this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
3004 * this.content.$element.append( '<p>This is a process dialog window. The header contains the title and two buttons: \'Cancel\' (a safe action) on the left and \'Done\' (a primary action) on the right.</p>' );
3005 * this.$body.append( this.content.$element );
3007 * MyProcessDialog.prototype.getActionProcess = function ( action ) {
3008 * var dialog = this;
3010 * return new OO.ui.Process( function () {
3011 * dialog.close( { action: action } );
3014 * return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
3017 * var windowManager = new OO.ui.WindowManager();
3018 * $( 'body' ).append( windowManager.$element );
3020 * var dialog = new MyProcessDialog();
3021 * windowManager.addWindows( [ dialog ] );
3022 * windowManager.openWindow( dialog );
3024 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
3028 * @extends OO.ui.Dialog
3031 * @param {Object} [config] Configuration options
3033 OO
.ui
.ProcessDialog
= function OoUiProcessDialog( config
) {
3034 // Parent constructor
3035 OO
.ui
.ProcessDialog
.parent
.call( this, config
);
3038 this.fitOnOpen
= false;
3041 this.$element
.addClass( 'oo-ui-processDialog' );
3046 OO
.inheritClass( OO
.ui
.ProcessDialog
, OO
.ui
.Dialog
);
3051 * Handle dismiss button click events.
3057 OO
.ui
.ProcessDialog
.prototype.onDismissErrorButtonClick = function () {
3062 * Handle retry button click events.
3064 * Hides errors and then tries again.
3068 OO
.ui
.ProcessDialog
.prototype.onRetryButtonClick = function () {
3070 this.executeAction( this.currentAction
);
3076 OO
.ui
.ProcessDialog
.prototype.onActionResize = function ( action
) {
3077 if ( this.actions
.isSpecial( action
) ) {
3080 return OO
.ui
.ProcessDialog
.parent
.prototype.onActionResize
.call( this, action
);
3086 OO
.ui
.ProcessDialog
.prototype.initialize = function () {
3088 OO
.ui
.ProcessDialog
.parent
.prototype.initialize
.call( this );
3091 this.$navigation
= $( '<div>' );
3092 this.$location
= $( '<div>' );
3093 this.$safeActions
= $( '<div>' );
3094 this.$primaryActions
= $( '<div>' );
3095 this.$otherActions
= $( '<div>' );
3096 this.dismissButton
= new OO
.ui
.ButtonWidget( {
3097 label
: OO
.ui
.msg( 'ooui-dialog-process-dismiss' )
3099 this.retryButton
= new OO
.ui
.ButtonWidget();
3100 this.$errors
= $( '<div>' );
3101 this.$errorsTitle
= $( '<div>' );
3104 this.dismissButton
.connect( this, { click
: 'onDismissErrorButtonClick' } );
3105 this.retryButton
.connect( this, { click
: 'onRetryButtonClick' } );
3108 this.title
.$element
.addClass( 'oo-ui-processDialog-title' );
3110 .append( this.title
.$element
)
3111 .addClass( 'oo-ui-processDialog-location' );
3112 this.$safeActions
.addClass( 'oo-ui-processDialog-actions-safe' );
3113 this.$primaryActions
.addClass( 'oo-ui-processDialog-actions-primary' );
3114 this.$otherActions
.addClass( 'oo-ui-processDialog-actions-other' );
3116 .addClass( 'oo-ui-processDialog-errors-title' )
3117 .text( OO
.ui
.msg( 'ooui-dialog-process-error' ) );
3119 .addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' )
3120 .append( this.$errorsTitle
, this.dismissButton
.$element
, this.retryButton
.$element
);
3122 .addClass( 'oo-ui-processDialog-content' )
3123 .append( this.$errors
);
3125 .addClass( 'oo-ui-processDialog-navigation' )
3126 // Note: Order of appends below is important. These are in the order
3127 // we want tab to go through them. Display-order is handled entirely
3128 // by CSS absolute-positioning. As such, primary actions like "done"
3130 .append( this.$primaryActions
, this.$location
, this.$safeActions
);
3131 this.$head
.append( this.$navigation
);
3132 this.$foot
.append( this.$otherActions
);
3138 OO
.ui
.ProcessDialog
.prototype.getActionWidgets = function ( actions
) {
3139 var i
, len
, widgets
= [];
3140 for ( i
= 0, len
= actions
.length
; i
< len
; i
++ ) {
3142 new OO
.ui
.ActionWidget( $.extend( { framed
: true }, actions
[ i
] ) )
3151 OO
.ui
.ProcessDialog
.prototype.attachActions = function () {
3152 var i
, len
, other
, special
, others
;
3155 OO
.ui
.ProcessDialog
.parent
.prototype.attachActions
.call( this );
3157 special
= this.actions
.getSpecial();
3158 others
= this.actions
.getOthers();
3159 if ( special
.primary
) {
3160 this.$primaryActions
.append( special
.primary
.$element
);
3162 for ( i
= 0, len
= others
.length
; i
< len
; i
++ ) {
3163 other
= others
[ i
];
3164 this.$otherActions
.append( other
.$element
);
3166 if ( special
.safe
) {
3167 this.$safeActions
.append( special
.safe
.$element
);
3171 this.$body
.css( 'bottom', this.$foot
.outerHeight( true ) );
3177 OO
.ui
.ProcessDialog
.prototype.executeAction = function ( action
) {
3179 return OO
.ui
.ProcessDialog
.parent
.prototype.executeAction
.call( this, action
)
3180 .fail( function ( errors
) {
3181 process
.showErrors( errors
|| [] );
3188 OO
.ui
.ProcessDialog
.prototype.setDimensions = function () {
3190 OO
.ui
.ProcessDialog
.parent
.prototype.setDimensions
.apply( this, arguments
);
3196 * Fit label between actions.
3201 OO
.ui
.ProcessDialog
.prototype.fitLabel = function () {
3202 var safeWidth
, primaryWidth
, biggerWidth
, labelWidth
, navigationWidth
, leftWidth
, rightWidth
,
3203 size
= this.getSizeProperties();
3205 if ( typeof size
.width
!== 'number' ) {
3206 if ( this.isOpened() ) {
3207 navigationWidth
= this.$head
.width() - 20;
3208 } else if ( this.isOpening() ) {
3209 if ( !this.fitOnOpen
) {
3210 // Size is relative and the dialog isn't open yet, so wait.
3211 this.manager
.opening
.done( this.fitLabel
.bind( this ) );
3212 this.fitOnOpen
= true;
3219 navigationWidth
= size
.width
- 20;
3222 safeWidth
= this.$safeActions
.is( ':visible' ) ? this.$safeActions
.width() : 0;
3223 primaryWidth
= this.$primaryActions
.is( ':visible' ) ? this.$primaryActions
.width() : 0;
3224 biggerWidth
= Math
.max( safeWidth
, primaryWidth
);
3226 labelWidth
= this.title
.$element
.width();
3228 if ( 2 * biggerWidth
+ labelWidth
< navigationWidth
) {
3229 // We have enough space to center the label
3230 leftWidth
= rightWidth
= biggerWidth
;
3232 // Let's hope we at least have enough space not to overlap, because we can't wrap the label…
3233 if ( this.getDir() === 'ltr' ) {
3234 leftWidth
= safeWidth
;
3235 rightWidth
= primaryWidth
;
3237 leftWidth
= primaryWidth
;
3238 rightWidth
= safeWidth
;
3242 this.$location
.css( { paddingLeft
: leftWidth
, paddingRight
: rightWidth
} );
3248 * Handle errors that occurred during accept or reject processes.
3251 * @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled
3253 OO
.ui
.ProcessDialog
.prototype.showErrors = function ( errors
) {
3254 var i
, len
, $item
, actions
,
3260 if ( errors
instanceof OO
.ui
.Error
) {
3261 errors
= [ errors
];
3264 for ( i
= 0, len
= errors
.length
; i
< len
; i
++ ) {
3265 if ( !errors
[ i
].isRecoverable() ) {
3266 recoverable
= false;
3268 if ( errors
[ i
].isWarning() ) {
3271 $item
= $( '<div>' )
3272 .addClass( 'oo-ui-processDialog-error' )
3273 .append( errors
[ i
].getMessage() );
3274 items
.push( $item
[ 0 ] );
3276 this.$errorItems
= $( items
);
3277 if ( recoverable
) {
3278 abilities
[ this.currentAction
] = true;
3279 // Copy the flags from the first matching action
3280 actions
= this.actions
.get( { actions
: this.currentAction
} );
3281 if ( actions
.length
) {
3282 this.retryButton
.clearFlags().setFlags( actions
[ 0 ].getFlags() );
3285 abilities
[ this.currentAction
] = false;
3286 this.actions
.setAbilities( abilities
);
3289 this.retryButton
.setLabel( OO
.ui
.msg( 'ooui-dialog-process-continue' ) );
3291 this.retryButton
.setLabel( OO
.ui
.msg( 'ooui-dialog-process-retry' ) );
3293 this.retryButton
.toggle( recoverable
);
3294 this.$errorsTitle
.after( this.$errorItems
);
3295 this.$errors
.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 );
3303 OO
.ui
.ProcessDialog
.prototype.hideErrors = function () {
3304 this.$errors
.addClass( 'oo-ui-element-hidden' );
3305 if ( this.$errorItems
) {
3306 this.$errorItems
.remove();
3307 this.$errorItems
= null;
3314 OO
.ui
.ProcessDialog
.prototype.getTeardownProcess = function ( data
) {
3316 return OO
.ui
.ProcessDialog
.parent
.prototype.getTeardownProcess
.call( this, data
)
3317 .first( function () {
3318 // Make sure to hide errors
3320 this.fitOnOpen
= false;
3329 * Lazy-initialize and return a global OO.ui.WindowManager instance, used by OO.ui.alert and
3333 * @return {OO.ui.WindowManager}
3335 OO
.ui
.getWindowManager = function () {
3336 if ( !OO
.ui
.windowManager
) {
3337 OO
.ui
.windowManager
= new OO
.ui
.WindowManager();
3338 $( 'body' ).append( OO
.ui
.windowManager
.$element
);
3339 OO
.ui
.windowManager
.addWindows( {
3340 messageDialog
: new OO
.ui
.MessageDialog()
3343 return OO
.ui
.windowManager
;
3347 * Display a quick modal alert dialog, using a OO.ui.MessageDialog. While the dialog is open, the
3348 * rest of the page will be dimmed out and the user won't be able to interact with it. The dialog
3349 * has only one action button, labelled "OK", clicking it will simply close the dialog.
3351 * A window manager is created automatically when this function is called for the first time.
3354 * OO.ui.alert( 'Something happened!' ).done( function () {
3355 * console.log( 'User closed the dialog.' );
3358 * @param {jQuery|string} text Message text to display
3359 * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
3360 * @return {jQuery.Promise} Promise resolved when the user closes the dialog
3362 OO
.ui
.alert = function ( text
, options
) {
3363 return OO
.ui
.getWindowManager().openWindow( 'messageDialog', $.extend( {
3366 actions
: [ OO
.ui
.MessageDialog
.static.actions
[ 0 ] ]
3367 }, options
) ).then( function ( opened
) {
3368 return opened
.then( function ( closing
) {
3369 return closing
.then( function () {
3370 return $.Deferred().resolve();
3377 * Display a quick modal confirmation dialog, using a OO.ui.MessageDialog. While the dialog is open,
3378 * the rest of the page will be dimmed out and the user won't be able to interact with it. The
3379 * dialog has two action buttons, one to confirm an operation (labelled "OK") and one to cancel it
3380 * (labelled "Cancel").
3382 * A window manager is created automatically when this function is called for the first time.
3385 * OO.ui.confirm( 'Are you sure?' ).done( function ( confirmed ) {
3386 * if ( confirmed ) {
3387 * console.log( 'User clicked "OK"!' );
3389 * console.log( 'User clicked "Cancel" or closed the dialog.' );
3393 * @param {jQuery|string} text Message text to display
3394 * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
3395 * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to
3396 * confirm, the promise will resolve to boolean `true`; otherwise, it will resolve to boolean
3399 OO
.ui
.confirm = function ( text
, options
) {
3400 return OO
.ui
.getWindowManager().openWindow( 'messageDialog', $.extend( {
3403 }, options
) ).then( function ( opened
) {
3404 return opened
.then( function ( closing
) {
3405 return closing
.then( function ( data
) {
3406 return $.Deferred().resolve( !!( data
&& data
.action
=== 'accept' ) );